`timescale 1ns / 1ps
//********************************************************************** 
// -------------------------------------------------------------------
// Disclaimers
// -------------------------------------------------------------------
// When you use this source file, please note that the author assumes 
// no legal or non-legal responsibility for any consequences of your use 
// of it including, but not limited to, the specific use of the code or 
// liability to you for infringement of any patent, copyright or other 
// intellectual property rights.
// If you do not agree to the terms, please do not use the file and 
// delete the file promptly.
// -------------------------------------------------------------------
// Copyright Notice
// -------------------------------------------------------------------
// This source file may be used for personal study, provided that this 
// copyright notice is not removed from the file.
// and that derivative works of this source file contain the original 
// copyright notice and related disclaimers.
// For commercial use, please contact the author for authorization, 
// otherwise the author reserves all rights.
// ------------------------------------------------------------------- 
// Author: Geeker_FPGA 
// Email:geeker_fpga@uisrc.com 
// Date:2022/03 
// Description: 
// top_tb
//
// 
// Web:http://www.uisrc.com
//------------------------------------------------------------------- 
//*********************************************************************/


// MDIO是Management Data Input/Output的缩写。MDIO接口包含在IEEE802.3协议中，
// 是专用于MAC与PHY管理的串行总线接口。主要用于配置PHY芯片状态、读取寄存器、读
// 取PHY地址、获取LINK状态等操作。与网口MII、RMII（TX_CLK、RX_CLK）等数据通讯
// 无关。MDIO接口最多可以挂载32个PHY设备。


module mdio_control_rw
(
	input   					clk                ,	// 输入时钟
	input   					rst_n              ,	// 复位信号，常1
	input          				write_start        ,	// 使能写出 状态
	input         	 			read_start         ,	// 使能读入 状态
	output reg           		busy			   ,	// 是否正在读取或发送
	input [4:0]    				phy_address        ,	// phy 芯片的物理地址
	input [4:0]    				register_address   ,	// 寄存器地址

	output reg           		write_done         ,	// 写出数据是否已完成（1时钟）
	input [15:0]         		write_register_data,	// 写出的寄存器数据
	
	output reg           		read_done          ,	// 读取数据是否完成（1时钟）
	output wire [15:0]   		read_register_data ,	// 读取数据存放的寄存器
	
	inout   					mdio               ,	// mdio 线
	output wire					mdc						// mdc  时钟线
);

wire        mdio_data_in  ;							// mdio 输入方向的数据
reg         mdio_data_out ;							// mdio 输出方向的数据
reg         mdio_direction;							// mdio 方向，0 表示输出， 1 表示输入
reg [15:0]  read_reg_temp ;							// mdio 读取到的数据

reg [4:0]   preamble_cnt  ;							// 已发送前导码的大小
reg [5:0]   data_cnt      ;							// 已发送数据的大小
reg [1:0]   STATE         ;							// 状态机 ，记录状态

localparam      IDLE 				= 2'd0;			// 空闲域。无MDIO帧发送时，MDIO接口输出高阻（730页）

localparam      SEND_PREAMBLE 	    = 2'd1;			// 前导。每帧发送前，MAC通过MDIO连续发送32个MDC周期的高电平，
													// 同时通过MDC输出32个时钟周期。前导的作用是为PHY建立同步提供
													// 时间。如果STA能够确定PHY可以接收管理帧，可以压缩前导的发送。（730页）

localparam      WRITE_REG 			= 2'd2;			// 写状态 OP 值为 01
localparam      READ_REG 			= 2'd3;			// 读状态 OP 值为 10




assign read_register_data = read_reg_temp;  
 
//mdio tri io
assign mdio = (mdio_direction == 0) ? mdio_data_out : 1'bz;
assign mdio_data_in = mdio;		 

// oddr 的作用
// 为了实现将时钟信号，通过普通的IO管脚实现输出。
EG_LOGIC_ODDR ODDR_clk(
	.q		(mdc	), 			// output 时钟输出
	.clk	(clk	), 			// input  时钟输入
	.d0		(1'b0	), 			// input  
	.d1		(1'b1	), 			// input
	.rst	(1'b0	)			// input
);    

always@(posedge clk or negedge rst_n)
begin

	// 复位寄存器
	if(!rst_n) 
	begin
		mdio_direction  <= 1'b1;
		mdio_data_out   <= 1'b0;
		read_reg_temp   <= 16'd0;
		write_done 		<= 1'b0;
		read_done  		<= 1'b0;
		preamble_cnt    <= 5'd0;
		data_cnt        <= 6'd0;
		busy			<= 1'b0;
		STATE 		    <= IDLE;
	end
	else 

	// 
	begin
		case(STATE)
			IDLE:
			    begin
					if(write_start | read_start)
					begin
						STATE <= SEND_PREAMBLE;
						busy  <= 1'b1;							// 标记正在开始发送
					end
					else
					begin
						STATE <= IDLE;
						busy  <= 1'b0;
					end
				end
			SEND_PREAMBLE:
				begin
					mdio_direction <= 1'b0;						// 修改方向为输出
					mdio_data_out  <= 1'b1;						// 修改输出的内容为1，准备要发送32个1
					if(preamble_cnt == 5'd31) 
					begin
						preamble_cnt <= 5'd0;					// 发送完32个1之后，修改状态
						if(write_start)
							STATE <= WRITE_REG;
						else
							STATE <= READ_REG;
					end
					else 
					begin
						preamble_cnt <= preamble_cnt + 1'b1;
					    STATE <= SEND_PREAMBLE;
					end
				end


			// MDIO 协议为 <PRE><ST><OP><PHYAD><REGAD><TA><DATA>
			// 其中
			// PRE(32) = 111111...  (32个1)
			// ST(2) = 01
			// OP(2) = READ ? 10 : 01
			// PHYAD(5) = phy address
			// REGAD(5) = register address
			// TA = READ ? Z0 : 10
			WRITE_REG:
				begin
					case(data_cnt)//write
						// ST = 01
						6'd0:  begin mdio_data_out <= 1'b0;                    data_cnt <= data_cnt + 1'b1; end
						6'd1:  begin mdio_data_out <= 1'b1;                    data_cnt <= data_cnt + 1'b1; end
						// OP = 01
						6'd2:  begin mdio_data_out <= 1'b0;                    data_cnt <= data_cnt + 1'b1; end
						6'd3:  begin mdio_data_out <= 1'b1;                    data_cnt <= data_cnt + 1'b1; end
						// 5 位地址
						6'd4:  begin mdio_data_out <= phy_address[4];          data_cnt <= data_cnt + 1'b1; end
						6'd5:  begin mdio_data_out <= phy_address[3];          data_cnt <= data_cnt + 1'b1; end
						6'd6:  begin mdio_data_out <= phy_address[2];          data_cnt <= data_cnt + 1'b1; end
						6'd7:  begin mdio_data_out <= phy_address[1];          data_cnt <= data_cnt + 1'b1; end
						6'd8:  begin mdio_data_out <= phy_address[0];          data_cnt <= data_cnt + 1'b1; end
						// 5 位寄存器位置
						6'd9:  begin mdio_data_out <= register_address[4];     data_cnt <= data_cnt + 1'b1; end
						6'd10: begin mdio_data_out <= register_address[3];     data_cnt <= data_cnt + 1'b1; end
						6'd11: begin mdio_data_out <= register_address[2];     data_cnt <= data_cnt + 1'b1; end
						6'd12: begin mdio_data_out <= register_address[1];     data_cnt <= data_cnt + 1'b1; end
						6'd13: begin mdio_data_out <= register_address[0];     data_cnt <= data_cnt + 1'b1; end
						// TA = 10
						6'd14: begin mdio_data_out <= 1'b1; 				   data_cnt <= data_cnt + 1'b1; end//TA 1
						6'd15: begin mdio_data_out <= 1'b0; 				   data_cnt <= data_cnt + 1'b1; end//TA 0
						// 16 位数据
						6'd16: begin mdio_data_out <= write_register_data[15]; data_cnt <= data_cnt + 1'b1; end
						6'd17: begin mdio_data_out <= write_register_data[14]; data_cnt <= data_cnt + 1'b1; end
						6'd18: begin mdio_data_out <= write_register_data[13]; data_cnt <= data_cnt + 1'b1; end
						6'd19: begin mdio_data_out <= write_register_data[12]; data_cnt <= data_cnt + 1'b1; end
						6'd20: begin mdio_data_out <= write_register_data[11]; data_cnt <= data_cnt + 1'b1; end
						6'd21: begin mdio_data_out <= write_register_data[10]; data_cnt <= data_cnt + 1'b1; end
						6'd22: begin mdio_data_out <= write_register_data[9];  data_cnt <= data_cnt + 1'b1; end
						6'd23: begin mdio_data_out <= write_register_data[8];  data_cnt <= data_cnt + 1'b1; end
						6'd24: begin mdio_data_out <= write_register_data[7];  data_cnt <= data_cnt + 1'b1; end
						6'd25: begin mdio_data_out <= write_register_data[6];  data_cnt <= data_cnt + 1'b1; end
						6'd26: begin mdio_data_out <= write_register_data[5];  data_cnt <= data_cnt + 1'b1; end
						6'd27: begin mdio_data_out <= write_register_data[4];  data_cnt <= data_cnt + 1'b1; end
						6'd28: begin mdio_data_out <= write_register_data[3];  data_cnt <= data_cnt + 1'b1; end
						6'd29: begin mdio_data_out <= write_register_data[2];  data_cnt <= data_cnt + 1'b1; end
						6'd30: begin mdio_data_out <= write_register_data[1];  data_cnt <= data_cnt + 1'b1; end
						6'd31: begin mdio_data_out <= write_register_data[0];
										write_done <= 1'b1; 				   data_cnt <= data_cnt + 1'b1; end
						
						// 写出完成
						6'd32: begin 
									 mdio_data_out   <= 1'b0; 
									 mdio_direction  <= 1'b1;
									 write_done      <= 1'b0;
									 data_cnt        <= 6'd0;
									 STATE           <= IDLE;
							   end
						default: begin data_cnt <= 6'd0; end
					endcase						
				end
			READ_REG:
				begin
					case(data_cnt)//read
						// ST = 01
						6'd0:  begin mdio_data_out     <= 1'b0;                                 data_cnt <= data_cnt + 1'b1; end
						6'd1:  begin mdio_data_out     <= 1'b1;                                 data_cnt <= data_cnt + 1'b1; end
						// OP = 10
						6'd2:  begin mdio_data_out     <= 1'b1;                                 data_cnt <= data_cnt + 1'b1; end 
						6'd3:  begin mdio_data_out     <= 1'b0;                                 data_cnt <= data_cnt + 1'b1; end
						// 5 位地址
						6'd4:  begin mdio_data_out     <= phy_address[4];                       data_cnt <= data_cnt + 1'b1; end
						6'd5:  begin mdio_data_out     <= phy_address[3];                       data_cnt <= data_cnt + 1'b1; end
						6'd6:  begin mdio_data_out     <= phy_address[2];                       data_cnt <= data_cnt + 1'b1; end
						6'd7:  begin mdio_data_out     <= phy_address[1];                       data_cnt <= data_cnt + 1'b1; end
						6'd8:  begin mdio_data_out     <= phy_address[0];                       data_cnt <= data_cnt + 1'b1; end
						// 5 位寄存器地址
						6'd9:  begin mdio_data_out     <= register_address[4];                  data_cnt <= data_cnt + 1'b1; end
						6'd10: begin mdio_data_out     <= register_address[3];                  data_cnt <= data_cnt + 1'b1; end
						6'd11: begin mdio_data_out     <= register_address[2];                  data_cnt <= data_cnt + 1'b1; end
						6'd12: begin mdio_data_out     <= register_address[1];                  data_cnt <= data_cnt + 1'b1; end
						6'd13: begin mdio_data_out     <= register_address[0];                  data_cnt <= data_cnt + 1'b1; end
						// TA = z0，修改mdio方向
						6'd14: begin mdio_data_out     <= 1'bz; mdio_direction <= 1'b1;         data_cnt <= data_cnt + 1'b1; end//Z
						6'd15: begin mdio_data_out     <= 1'b0; 						        data_cnt <= data_cnt + 1'b1; end//0
						// 将16位数据读取到 read_reg_temp
						6'd16: begin read_reg_temp[15] <= mdio_data_in;                         data_cnt <= data_cnt + 1'b1; end
						6'd17: begin read_reg_temp[14] <= mdio_data_in;                         data_cnt <= data_cnt + 1'b1; end
						6'd18: begin read_reg_temp[13] <= mdio_data_in;                         data_cnt <= data_cnt + 1'b1; end
						6'd19: begin read_reg_temp[12] <= mdio_data_in;                         data_cnt <= data_cnt + 1'b1; end
						6'd20: begin read_reg_temp[11] <= mdio_data_in;                         data_cnt <= data_cnt + 1'b1; end
						6'd21: begin read_reg_temp[10] <= mdio_data_in;                         data_cnt <= data_cnt + 1'b1; end
						6'd22: begin read_reg_temp[9]  <= mdio_data_in;                         data_cnt <= data_cnt + 1'b1; end
						6'd23: begin read_reg_temp[8]  <= mdio_data_in;                         data_cnt <= data_cnt + 1'b1; end
						6'd24: begin read_reg_temp[7]  <= mdio_data_in;                         data_cnt <= data_cnt + 1'b1; end
						6'd25: begin read_reg_temp[6]  <= mdio_data_in;                         data_cnt <= data_cnt + 1'b1; end
						6'd26: begin read_reg_temp[5]  <= mdio_data_in;                         data_cnt <= data_cnt + 1'b1; end
						6'd27: begin read_reg_temp[4]  <= mdio_data_in;                         data_cnt <= data_cnt + 1'b1; end
						6'd28: begin read_reg_temp[3]  <= mdio_data_in;                         data_cnt <= data_cnt + 1'b1; end
						6'd29: begin read_reg_temp[2]  <= mdio_data_in;                         data_cnt <= data_cnt + 1'b1; end
						6'd30: begin read_reg_temp[1]  <= mdio_data_in;                         data_cnt <= data_cnt + 1'b1; end
						6'd31: begin read_reg_temp[0]  <= mdio_data_in; 
											read_done  <= 1'b1;                                 data_cnt <= data_cnt + 1'b1; end
						// 读取完毕
						6'd32: begin 
										read_reg_temp  <= 16'd0;
										read_done      <= 1'b0;										 
										data_cnt       <= 6'd0;
										STATE          <= IDLE;
							   end
						default: begin data_cnt <= 6'd0; end
					endcase		
				end
		endcase
	end
end
	
endmodule
